;;; ----------------------------------------------------------------------
;;; A DOS for SD Cards on 6502 systems, based on earlier 6522IDE and
;;; Symbiosys work
;;; ----------------------------------------------------------------------
;;; SD Card controller (doesn't work with MMC cards yet)
;;; by Dolo Miah (C) 2014-2015
;;; https://hackaday.io/dolomiah
;;; https://hackaday.io/project/5789-6502-homebrew-computer
;;; ---
;;; Chris Baird,, <cjb@brushtail.apana.org.au> 2017

;;; directory record format:
;;; 00-1A name, null-terminated
;;; 1B,1C start address
;;; 1D,1E end address
;;; 1F          00=empty/deleted
;;;             FF=end of directory  (..optional)
;;;             01=flat file
;;;             02- ...
;;; file location segment determined from dirindex

;;; ca65 -l abiosys.asm && ld65 -t none abiosys.o && scp a.out root@otter:
;;; ssh root@otter "~/andmodem 49152 a.out"

            .p02

            VIC20       = 0
            CBM64       = 1

            EPROM       = 0 ; disable the needs-lots-of-ram commands

;;; ----------------------------------------------------------------------

            .if VIC20 = 1 || CBM64 = 1

            CHROUT      = $FFD2
            CHRIN       = $FFCF
            STOP        = $FFE1
            CURSORPOS   = 211
            SCREENPTR   = 209

            .endif

            ;;

            .if VIC20 = 1

	    PORT	= $9110
	    DDR 	= $9112
            SD_MOSI     = %00000010 ; PB1
	    SD_CS       = %00000100 ; PB2
            SD_CLK      = %00100000 ; PB5
            SD_MISO     = %01000000 ; PB6
            SCREENWID   = 22
            .org 40960

            .endif

            ;;

            .if CBM64 = 1

	    PORT	= $DD01
	    DDR 	= $DD03
            SD_MOSI     = %00010000 ; PB1
	    SD_CS       = %00001000 ; PB2
            SD_CLK      = %00000100 ; PB3
            SD_MISO     = %00000010 ; PB4
            SCREENWID   = 40
            .org 49152

            .endif

;;; ----------------------------------------------------------------------

            PROMPTCHAR  = 46
            commandkey  = 165 ; I wonder how interrupt-safe these are..
            hasname     = 150
            hasaddr     = 155
            stringstart = 166
            stringend   = 167
            ADDR1       = 168 ; .word
            ADDR2       = 170 ; .word .. and keep it adjacent to ADDR1
            FTYPETMP    = 177
            BUFPTR      = 178   ; .word

            shifting    = 176
            LBA0        = 828
            LBA8        = 829
            LBA16       = 830
            SECTORSIZE  = 832
            CURDIRBLK   = 833
            DIRINDEX    = 834   ; .word

            LINPUT      = 836
            DIRBUFFER   = 896   ; $380

	    FILE_NAME   = DIRBUFFER
            FILE_ADDR_L = DIRBUFFER+27
            FILE_ADDR_H = DIRBUFFER+28
            FILE_LEN_L  = DIRBUFFER+29
            FILE_LEN_H  = DIRBUFFER+30
            FILE_TYPE   = DIRBUFFER+31

            BIGDIRLIST = 1

;;; ----------------------------------------------------------------------
;;; ----------------------------------------------------------------------

START:      jmp CLIMAIN

            ;; BUFPTR      = 178
            ;; LBA0        = 828
            ;; LBA8        = 829
            ;; LBA16       = 830
            ;; DIRINDEX    = 834
            ;; DIRBUFFER   = 896   ; $380
            ;; FILE_NAME   = DIRBUFFER
            ;; FILE_ADDR_L = DIRBUFFER+27
            ;; FILE_ADDR_H = DIRBUFFER+28
            ;; FILE_LEN_L  = DIRBUFFER+29
            ;; FILE_LEN_H  = DIRBUFFER+30
            ;; FILE_TYPE   = DIRBUFFER+31
            ;;
            ;; DISK_INIT   = $C003
            ;; READSECTOR  = $C006
            ;; WRITESECTOR = $C009
            ;; FINDNAMEPTR = $C00C
            ;; FINDEMPTY   = $C00F
            ;; SEEKSEGMENT = $C012

;;; No requirements

            jmp Disk_Init

;;; Sector in LBA0,LBA8,LBA16 (828,829,830)
;;; Destination in BUFPTR (178,179)
;;; Returns SD response byte in A

            jmp ReadSector

;;; Sector in LBA0,LBA8,LBA16 (828,829,830)
;;; Source in BUFPTR (178,179)
;;; Returns SD response byte in A

            jmp WriteSector

;;; Start of null-terminated string in (X,A)
;;; prepares LINPUT,stringstart,stringend
;;; (Name in LINPUT(836-895); start=stringstart(166), stringend(167))
;;; Carry set if failed; DIRINDEX(834,835) set if successful
;;; X & Y & A hold the DIRBUFFER offset to the struct

            jmp FindNamePtr
            jmp FindEmpty

;;; Refers to DIRINDEX(834,835) and set LBA0/LBA8/LBA16

            jmp SeekSegment


;;; ----------------------------------------------------------------------

Disk_Init:  ;; configure hardware
            lda DDR
            ora #SD_CS+SD_CLK+SD_MOSI
            and #~SD_MISO
            sta DDR

            ;; initial state of DOS params
            lda #$FF
            sta CURDIRBLK       ; scratch directory cache

            ;jmp Init_SD

;;;

Init_SD:    lda PORT            ; Unselect device
            ora #SD_CS
            sta PORT

            and #~SD_CLK        ; Set clock low
            sta PORT

            ora #SD_MOSI        ; DI/MOSI high
            sta PORT

	    ldx #8
	    jsr long_delay

	    ldx #8              ; 10 bytes of 255
initsd1:    lda #$FF
            jsr sd_sendbyte     ; Send the $FF byte
	    dex
	    bne initsd1
            ;;

            lda PORT            ; Unselect device
            ora #SD_CS
            sta PORT
            ;;

            jsr sd_sendcmd0
	    cmp #$FF            ; $FF is not a valid response
	    beq Init_SD
            ;;

initacmd41: jsr sd_sendcmd55
	    jsr sd_sendcmd41
	    cmp #0              ; Was R1 = 0
	    bne initacmd41      ; Retry if not

            jmp sd_sendcmd16

;;;

long_delay: lda #0
longdelay1: nop
            nop
            nop
            nop
            clc
            adc #1
            bne longdelay1
            dex
            bne longdelay1
AnRTS:      rts

;;; ----------------------------------------------------------------------
;; End a cmd frame by sending CS high

.macro jsr_sd_endcmd_nopush
            lda PORT
            ora #SD_CS          ; Chip select bit
            sta PORT            ; First set it high
.endmacro

.macro jsr_sd_endcmd
            pha
            jsr_sd_endcmd_nopush
	    pla
.endmacro

;;; ----------------------------------------------------------------------

sd_sendbyte:
	    sta shifting        ; For shifting out
	    pha
            tya
            pha
            lda shifting

	    ldy #8              ; 8 bits to shift out

sd_shiftoutbit:
	    lda PORT
	    asl shifting        ; Shift out MSB
	    bcc sd_shiftoutlo   ; Skip over setting high if no carry
            ora #SD_MOSI
            .byte $2C
sd_shiftoutlo:
            and #~SD_MOSI
            sta PORT            ; Set MOSI

sd_shiftskiplo:
            ora #SD_CLK         ; Clock high
            sta PORT
	    and #~SD_CLK
	    sta PORT            ; Clock low (pulse will transfer bit)

	    dey                 ; Count bits
	    bne sd_shiftoutbit  ; Until no more bits to send

            pla
            tay
	    pla
	    rts

;;;

.macro jsr_sd_startcmd_nopush
	    jsr sd_sendsync     ; Delay / synch pulses
.endmacro

;;; Normally Sync is done by sending $FF (MOSI=1) and CLK'ing; this
;;; is a faster way.
;;; Dolo mentions 2x $FF before every command got things to work;
;;; 11 sync pulses seem to be required, at least on a 1MHz VIC20

sd_sendsync:
            lda PORT
            ora #SD_MOSI

            ora #SD_CLK
            tax
            and #~SD_CLK
            tay
            stx PORT
            sty PORT
            stx PORT
            sty PORT
            stx PORT
            sty PORT
            stx PORT
            sty PORT
            stx PORT
            sty PORT
            stx PORT
            sty PORT
            stx PORT
            sty PORT
            stx PORT
            sty PORT
            stx PORT
            sty PORT
            stx PORT
            sty PORT
            stx PORT
            sty PORT
            and #~SD_CS
            sta PORT            ; Now set it low
            rts

;;; ----------------------------------------------------------------------

sd_getbyte: tya
            pha
            txa
            pha

	    lda #SD_MOSI        ; Set MOSI high
            ora PORT
	    sta PORT

	    ldy #8              ; Shift in the 8 bits
sd_shiftinbit:
            lda PORT
	    ora #SD_CLK         ; Toggle clock line
            sta PORT            ; Low->High
	    ldx PORT            ; Sample SD card lines
            and #~SD_CLK
	    sta PORT            ; High->Low
	    clc                 ; Clear to shift in zero
	    txa
	    and #SD_MISO        ; Check MISO/DO bit
	    beq sd_shiftinhi    ; Don't set carry flag if zero
	    sec                 ; Set to shift in one
sd_shiftinhi:
	    rol shifting        ; Rotate carry state in to shifting
	    dey                 ; Next bit
	    bne sd_shiftinbit

            pla
            tax
            pla
            tay
	    lda shifting           ; Return response in A
	    rts

;;; ----------------------------------------------------------------------
;;; Low level get response routine

sd_getrespbyte:
            jsr sd_getbyte
	    cmp #$FF            ; Keep reading MISO until not FF
	    beq sd_getrespbyte
	    rts

;;; ----------------------------------------------------------------------
;;; Low level busy check routine

sd_busy:    pha
sd_isbusy:  jsr sd_getbyte
	    cmp #$FF            ; Keep reading MISO until FF
	    bne sd_isbusy
	    pla
	    rts

;;; ----------------------------------------------------------------------
;;; Send CMD0

sd_sendcmd0:
	    jsr_sd_startcmd_nopush ; Delay / synch pulses
	    ;; Send $40, 0,0,0,0, $95
	    lda #64

;;;

sd_sendcmdA:
	    jsr sd_sendbyte
	    lda #0
	    jsr sd_sendbyte
	    jsr sd_sendbyte
	    jsr sd_sendbyte
	    jsr sd_sendbyte
	    lda #$95           ; Checksum needs to be right
	    jsr sd_sendbyte
	    jsr sd_getrespR1    ; Get the response
	    jsr_sd_endcmd
            rts

;;; ----------------------------------------------------------------------
;;; Send CMD55

sd_sendcmd55:
	    jsr_sd_startcmd_nopush ; Delay / synch pulses
	    ;; Send $40+55, 0,0,0,0, $95
	    lda #64+55
            bne sd_sendcmdA

;;; ----------------------------------------------------------------------
;;; Send ACMD41

sd_sendcmd41:
	    jsr_sd_startcmd_nopush ; Delay / synch pulses

	    ;; Send 64+41, 0, 0, 0, 0, $95
	    lda #64+41
            bne sd_sendcmdA

;;; ----------------------------------------------------------------------
;;; Send CMD16

sd_sendcmd16:
	    jsr_sd_startcmd_nopush ; Delay / synch pulses

	    ;; Send 64+16, 0, 0, 1, 0, $95
	    lda #64+16
	    jsr sd_sendbyte
	    lda #0
	    jsr sd_sendbyte
	    jsr sd_sendbyte
	    lda #1              ; 0x100 block size = 256 bytes
	    jsr sd_sendbyte
	    lda #0
	    jsr sd_sendbyte
	    lda #$95            ; Checksum needs to be right
	    jsr sd_sendbyte
	    jsr sd_getrespR1    ; Get the response
	    jsr_sd_endcmd
            rts

;;; ----------------------------------------------------------------------
;;; Low level get response R1

            sd_getrespR1 = sd_getrespbyte

;;; ----------------------------------------------------------------------
;;; Send CMD17

ReadSector:
sd_sendcmd17_256:
	    jsr_sd_startcmd_nopush     ; Delay / synch pulses
	    ;; Send 64+17, XX, XX, XX, XX, $95
	    lda #64+17
	    jsr sd_sendbyte
	    lda LBA16              ; only reads 'pages'
	    jsr sd_sendbyte
	    lda LBA8
	    jsr sd_sendbyte
	    lda LBA0
	    jsr sd_sendbyte
	    lda #0
	    jsr sd_sendbyte
	    lda #$95            ; Checksum needs to be right
	    jsr sd_sendbyte
	    jsr sd_getrespbyte
	    pha                 ; Save response

sd_gr171:   jsr sd_getbyte      ; Get a byte
	    cmp #$FE            ; Is it the token?
	    bne sd_gr171        ; No

	    ldy #0
sd_gr172:   jsr sd_getbyte      ; get a byte
            cpy SECTORSIZE
            beq rs3
            bcs rs2
rs3:        sta (BUFPTR),y
rs2:        iny
	    bne sd_gr172        ; Until all bytes read

            jsr sd_getbyte      ; CRC
	    jsr sd_getbyte      ; CRC

	    jsr sd_busy         ; Wait for card to be ready
	    jsr_sd_endcmd_nopush
	    pla                 ; Restore the response byte
            rts

;;;

;;; ----------------------------------------------------------------------
;;; Send CMD24
;;; Doesn't preserve regs
;;; Doesn't support SECTORSIZE

WriteSector:
sd_sendcmd24_256:
            jsr hideroms

	    jsr_sd_startcmd_nopush ; Delay / synch pulses
	    ;; Send 64+24, XX, XX, XX, XX, $95
	    lda #64+24
	    jsr sd_sendbyte
	    lda LBA16
	    jsr sd_sendbyte
	    lda LBA8
	    jsr sd_sendbyte
	    lda LBA0
	    jsr sd_sendbyte
	    lda #0
	    jsr sd_sendbyte
	    lda #$95            ; Checksum needs to be right
	    jsr sd_sendbyte
	    jsr sd_getrespbyte  ; Get response
	    jsr sd_getbyte
	    lda #$FE            ; Start of data token
	    jsr sd_sendbyte

	    ldy #0
sd_write:   lda (BUFPTR), y
	    jsr sd_sendbyte
	    iny
	    bne sd_write


	    lda #$aa            ; Arbitrary CRC bytes
	    jsr sd_sendbyte
	    jsr sd_sendbyte

	    jsr sd_getbyte      ; Get data response byte
	    pha                 ; Save it to return

sd_waitforwritecomplete:
	    jsr sd_busy         ; Wait for card to be ready
	    jsr_sd_endcmd_nopush ; Release the card
            pla
            jmp showroms

;;; ----------------------------------------------------------------------
;;; ----------------------------------------------------------------------
;;; The directory exists on blocks 0-255, so LBA8 should always end up as 0

BumpDirIndex:
            inc DIRINDEX
            bne bdi1
            inc DIRINDEX+1
bdi1:       rts

;;;

ResetDirScan:
            ldy #1
            sty DIRINDEX
            dey
            sty DIRINDEX+1
            dey                 ; attempt to force a refetch
            sty CURDIRBLK
            ;jmp ReadDirectory

;;;

ReadDirectory:
            lda #127
            sta SECTORSIZE
            jsr SetDirBufptr
            jsr CalcDirBlk
            cmp CURDIRBLK
            beq CalcDirOff
            sta CURDIRBLK
            jsr ReadSector      ; should really catch Carry flag signals..

CalcDirOff: lda DIRINDEX
	    lsr a
            ror a
            ror a
            ror a
            and #96
            tax
            tay
            rts

;;;

UpdateDirectory:
            jsr CalcDirBlk
            jsr SetDirBufptr
            jmp WriteSector

;;;

CalcDirBlk: lda #0
            sta LBA16
            lda DIRINDEX+1
            sta LBA8
            lda DIRINDEX
            lsr LBA8
            ror a
            lsr LBA8
            ror a
            sta LBA0
            rts

;;;

SetDirBufptr:
            lda #<DIRBUFFER
            sta BUFPTR
            lda #>DIRBUFFER
            sta BUFPTR+1
            rts


;;; ----------------------------------------------------------------------

FindNamePtr:
            stx BUFPTR
            sta BUFPTR+1
            ldy #0
            sty stringstart
findnp1:    lda (BUFPTR),y
            sta LINPUT,y
            cmp #0
            beq findnp2
            iny
            bne findnp1

findnp2:    cpy #0
            beq findnp3
            dey
findnp3:    sty stringend
            ;jmp FindName

;;; ----------------------------------------------------------------------
;;; Carry set if failed

FindName:   lda #1
            .byte $2c
FindEmpty:  lda #0
            sta FTYPETMP
            ;;

            jsr ResetDirScan
findloop:   jsr ReadDirectory
            lda FILE_TYPE,x
            cmp #255
            beq findend
            cmp FTYPETMP
            bcc findnext        ; hopefully gets 01 and 02
            cmp #0              ; looking for a deleted/null record
            beq findfound       ; exit now if =0 (and accept >1)

            ;; compare filename w/ wildcarding

            ldy stringstart
findloop1:  lda LINPUT,y
            cmp #'*'
            beq findfound        ; ..but needs carry cleared
            cmp #'?'
            beq findbr1
            cmp FILE_NAME,x
            bne findnext

findbr1:    inx
            iny
            lda FILE_NAME,x
            bne findloop1
            dey
            cpy stringend       ; check lengths the same..
            bne findnext

findfound:  jsr ReadDirectory   ; restore ReadDirectory's X&Y info
            clc
            bcc findend

findnext:   jsr BumpDirIndex
            bne findloop

;;;

SeekSegment: ;; LBA = 256 * dirindex
            lda #0
            sta LBA0
            lda DIRINDEX
            sta LBA8
            lda DIRINDEX+1
            sta LBA16

findend:    rts


;;; ----------------------------------------------------------------------

;;; 1kB EPROM inbuilt commands
;;;
;;; .L "name[" saddr]           Load
;;; ."name["] (no commandchar)  Load and $C533/$C659/$C7AE (RUN)
;;; .S "name[" saddr eaddr]     Save
;;; .$                          "Minimal" Directory listing
;;; .G addr                     Go/Execute machine code
;;; .P [n]                      Load and JMP n'th program on disk
;;; .- "name["]                 Delete
;;; .R                          Cold Reset
;;; .X                          Exit to BASIC/Break

;;; Extended command set (when built for RAM)
;;;
;;; .$                          "Full details" Directory listing
;;; .Q                          Jump back to EPROM
;;; .? "name["] or nnnn         Verbose directory record print of file/index,
;;;                             (and copy record into dirbuffer)
;;; .N "name"                   Change name in dirbuffer
;;; .T nn                       Set dirbuf filetype (0=deleted,1=file,FF=EOD)
;;; .I addr [len]               Change SADDR, FILE_LEN in dirbuffer
;;; .J nn "name                 'Juggle' dirrecord for name and index nn
;;;                             (check that T=FF isn't involved)
;;; .V "name[" saddr]           Verify
;;; .Z                          Listing of deleted & EOD files

;;; Maybe commands
;;;
;;; .& "name"                   &Modem download
;;; .U "name"                   &Modem upload
;;; .U [nnnn]                   Set 'User' (aka IDE_LBA24)
;;; .H                          Usage summary
;;; .addr addr                  Memory dump
;;; .:addr nn                   Single byte poke

;;; Getting a directory..
;;; .P        Autoexecs the first file on disk (RAM version of 6522IDE)
;;; .$        ..which will trash the program currently in memory..
;;; .Q        ..but V2 BASIC users have lived with that for 40 years :)

;;; How to rename a file..
;;; .? "TEST001"
;;; .N "TEST.BAK"

;;; Moving the EOD marker..
;;; .? 007F
;;; .T 00
;;; .? 03FF             now handles 1024 files, but directory searches
;;; .T FF               are slower

;;; Making a machine code program a 'P' command
;;; .S "SUPERMON A0" A000 AFFF
;;; .J 02 "SUPERMON A0"
;;; .P2

;;; 00 = deleted
;;; 01 = plain file with loading address and length
;;; 02 = 'block' file that uses all 64kB (i.e FORTH block files)
;;; FF = deleted

;;; WKOY !#%'()=+@*;[]/

;;; ----------------------------------------------------------------------

CLIMAIN:    jsr Disk_Init

main0:      jsr CROUT
main:       lda #PROMPTCHAR
main2:      jsr CHROUT
            cmp #PROMPTCHAR
            bne main0

            ldy #4              ; defaults for load/save/?go
mainl1:     lda 42,y
            sta ADDR1-1,y
            dey
            bne mainl1

mainrd:     jsr CHRIN
            sta LINPUT,y
            ;; should probably catch Y overflowing LINPUT
            iny
            cmp #13
            bne mainrd

            ldy #0
            sty stringend
            sty stringstart
            sty commandkey
            sty hasaddr
            sty hasname
            dey

            ;;

parse:      iny
            lda LINPUT,y
            cmp #13
            beq exec
            cmp #33
            bcc parse
            cmp #','
            beq parse
            cmp #34
            beq gotquote

            ldx hasname         ; loop if quoting
            cpx #2
            bcs parse1
            cpx #0
            bne parse

parse1:     eor #48
            cmp #10
            bcc adddigit

            adc #$88
            cmp #$fa
            bcc nothex

adddigit:   inc hasaddr
            asl
            asl
            asl
            asl
            ldx #4
hexshift:   asl
            rol ADDR1
            rol ADDR1+1
            rol ADDR2
            rol ADDR2+1
            dex
            bne hexshift
            beq parse

nothex:     lda LINPUT,y
            sta commandkey
            bne parse

gotquote:   ldx stringend
            stx stringstart
            sty stringend
            inc hasname
            bne parse

            ;;

exec:       jsr CHROUT          ; this'll be a CR
            cpy #1              ; empty line?
            bcs execb0          ; because main is 132 bytes away..
            jmp main

execb0:     ldx hasname         ; check if quote not closed
            cpx #1
            bne execb1
            ldx stringend
            stx stringstart
            sty stringend

execb1:     ldx stringstart
            cpx stringend       ; check if empty string
            beq keycmds

            dec stringend
            bpl *+5
	    jmp errnullstr      ; if no quote at all
            inc stringstart

            ;; ----------------------------------------

;;; yes, this should become table-based..
.macro COMMAND KEY, VECTOR
.local BRANCH
            cmp #KEY
            bne BRANCH
            jmp VECTOR
BRANCH:
.endmacro

keycmds:    lda commandkey
            COMMAND 'Q', 40000
            COMMAND '?', query
            COMMAND 'N', rename
	    COMMAND 'T', changetype
	    COMMAND 'I', changeinfo
            .if EPROM = 0
            COMMAND 'J', juggle
            .endif
	    COMMAND '!', error
            COMMAND 'Z', zappedlist
	    COMMAND '$', dirlisting
	    COMMAND 'V', verify
	    COMMAND 'L', load
            COMMAND '-', delete
            COMMAND 'R', reset
            COMMAND 'X', AnRTS
            COMMAND 'P', Program
            COMMAND 'G', run
            COMMAND 'S', save
            cmp #0
            beq load
;;;

errbadcmd:  lda #'?'
            .byte $2c
errnostr:
            lda #34
            .byte $2c
errnullstr:
            lda #'0'
            .byte $2c

errnofile:  lda #'X'

error:      jsr fullerror
            jmp main2


;;; ----------------------------------------------------------------------

reset:      .if VIC20 = 1
            jmp 64802
            .endif
            .if CBM64 = 1
            jmp 64738
            .endif

run:        jmp (ADDR1)

;;; ----------------------------------------------------------------------
;;; .P [N]  N=[0..F]
;;; Loads program at dirindex[N] and jumps to loading address immediately
;;; Defaults to "P 1" if P is entered alone (due to ADDR1 = *43 = 1)

Program:    jsr ResetDirScan
            lda ADDR1
            and #15
            sta DIRINDEX
            lda #0
            sta DIRINDEX+1
            jsr ReadDirectory
            jsr setloadaddr
            lda BUFPTR
            sta ADDR1
            lda BUFPTR+1
            sta ADDR1+1
            jsr loading
            jmp (ADDR1)


;;; ----------------------------------------------------------------------
;;; .- "filename"

delete:     jsr FindName
            bcc delb1
            jmp errnofile

delb1:      lda #0
            sta FILE_TYPE,x
            jmp saveend


;;; ----------------------------------------------------------------------

load:       lda hasname
            beq errnostr
            jsr FindName
            bcs errnofile

            jsr setloadaddr

            ldy hasaddr        ; check if a load addr given
            beq load1

            lda ADDR1
            sta BUFPTR
            lda ADDR1+1
            sta BUFPTR+1

load1:      clc
            lda BUFPTR
            sta 43
            adc FILE_LEN_L,x
            sta 45
            lda BUFPTR+1
            sta 44
            adc FILE_LEN_H,x
            sta 46

            jsr loading

            lda commandkey
            bne load2
            .if VIC20 = 1
            jsr $C659           ; run BASIC program
            jsr $C533
            jmp $C7AE
            .endif
            .if CBM64 = 1
            jsr $A659
            jsr $A533
            jmp $A7AE
            .endif

load2:      jmp main


;;; ----------------------------------------------------------------------

save:       lda hasname
            bne saves1
            jmp errnostr

saves1:     lda hasaddr
            cmp #8
            beq saves2

            jsr swapaddrs       ; if no args, these need to be swapped

saves2:     jsr FindName
            bcc save1           ; if name exists, will overwrite
            jsr FindEmpty
            bcc save1           ; if not, find an empty slot

errdiskfull:
            lda #'Z'
            jmp error

save1:      ;; update (in-memory currently) the directory info
            ;; (ADDR1 gets trashed later)
            ;; ADDR1 = ending address
            ;; ADDR2 = load address

            lda ADDR2
            sta BUFPTR
            sta FILE_ADDR_L,x
            lda ADDR2+1
            sta BUFPTR+1
            sta FILE_ADDR_H,x

            sec                 ; ADDR1 -= ADDR2
            lda ADDR1
            sbc BUFPTR
            sta FILE_LEN_L,x
            sta ADDR1
            lda ADDR1+1
            sbc BUFPTR+1
            sta FILE_LEN_H,x
            sta ADDR1+1

            lda #1
            sta FILE_TYPE,x

            ;; copy in the filename (safe to trash X)

            ldy stringstart

save2:      lda LINPUT,y
            sta FILE_NAME,x
            ;; filename length check
            txa
            and #31
            cmp #(FILE_ADDR_L-DIRBUFFER-2)
            bcs save3
            inx
save3:      iny
            cpy stringend
            bcc save2
            beq save2
            lda #0
            sta FILE_NAME,x

            ;; write memory to segment[dirindex]
            ;; it doesn't matter if the entire page is written,
            ;; so no fine boundary checking

            jsr SeekSegment

saveloop:   jsr WriteSector
            inc BUFPTR+1
            inc LBA0

            lda ADDR1+1
            jsr spinner
            dec ADDR1+1
            lda ADDR1+1
            cmp #255
            bne saveloop

            ;;

saveend:    jsr UpdateDirectory
            jmp main

;;; ----------------------------------------------------------------------

spinner:    and #7
            tay
            lda spinchars,y
            ldy CURSORPOS
            sta (SCREENPTR),y
            rts

spinchars:  .byte "<(!)>)!("

;;; ----------------------------------------------------------------------

dirlisting: jsr ResetDirScan

dlistloop:  jsr STOP
            beq dlistexit

            jsr ReadDirectory   ; A/X/Y have the offset..
            lda FILE_TYPE,y
            beq dlistnext
            cmp #255
            beq dlistexit

dlist1:     lda FILE_NAME,x
            jsr CHROUT
            inx
            cmp #0
            bne dlist1

            .ifdef BIGDIRLIST   ; --------------------------------------
dlist4:     lda FILE_TYPE,y     ; handle type=2 'block' files
            cmp #2
            bne dlist8

            lda CURSORPOS
            cmp #SCREENWID-1
            bcs dlist7
            lda #SCREENWID-2
            sta CURSORPOS
            lda #'B'
            jsr CHROUT
            jmp dlist7

dlist8:     lda CURSORPOS       ; handle plain files
            cmp #SCREENWID-10
            bcc dlist6
            lda #SCREENWID-5
            .byte $2c
dlist6:     lda #SCREENWID-10
            sta CURSORPOS
            lda FILE_ADDR_H,y
            ldx FILE_ADDR_L,y
            jsr HEX16OUT
            lda CURSORPOS
            cmp #SCREENWID-5
            bcs dlist7
            inc CURSORPOS
            lda FILE_LEN_H,y
            ldx FILE_LEN_L,y
            jsr HEX16OUT
            .endif              ; --------------------------------------
dlist7:     jsr CROUT

dlistnext:  jsr BumpDirIndex
            bne dlistloop

dlistexit:  jmp main


;;; ----------------------------------------------------------------------
;;; shuffled out for byte-saving

loading:    jsr SeekSegment

loadloop1:  ldx #255
            lda ADDR2+1
            bne loadbr1
            ldx ADDR2
loadbr1:    lda ADDR2+1
            jsr spinner
            stx SECTORSIZE
            jsr ReadSector
            inc BUFPTR+1
            inc LBA0
            dec ADDR2+1
            lda ADDR2+1
            cmp #255
            bne loadloop1
            clc
            rts

;;;

swapaddrs:  lda ADDR1           ; trashes Y
            ldy ADDR2
            sta ADDR2
            sty ADDR1
            lda ADDR1+1
            ldy ADDR2+1
            sta ADDR2+1
            sty ADDR1+1
            rts

;;;

setloadaddr:
            lda FILE_ADDR_L,x
            sta BUFPTR
            lda FILE_ADDR_H,x
            sta BUFPTR+1
            lda FILE_LEN_H,x
            sta ADDR2+1
            lda FILE_LEN_L,x
            sta ADDR2
            rts


;;; ----------------------------------------------------------------------

HEX16OUT:   jsr HEX8OUT         ; A=hi byte, X=lo byte
            txa
HEX8OUT:    pha                 ; A=value
            lsr a
            lsr a
            lsr a
            lsr a
            jsr HEX4OUT
            pla
HEX4OUT:    and #15
            ora #48
            cmp #58
            bcc hex1
            adc #6
            .byte $2c
CROUT:      lda #13
hex1:       jmp CHROUT


;;; ----------------------------------------------------------------------

query:      lda hasname
            beq queryb0
            jsr FindName
            bcc queryprint

queryb0:    lda hasaddr
            beq errnoindex
            jsr maskaddrs
            lda ADDR1
            sta DIRINDEX
            lda ADDR1+1
            sta DIRINDEX+1
            lda CURDIRBLK
            jsr ReadDirectory
            lda CURDIRBLK

            ;; maybe encode unprintable chars?

queryprint: jsr CalcDirOff      ; xxx rename needs this

            lda DIRINDEX+1
            ldx DIRINDEX
            jsr HEX16OUT

            inc CURSORPOS
            lda FILE_TYPE,y
            jsr HEX8OUT
            inc CURSORPOS

            lda FILE_TYPE,y
            cmp #2
            bcc queryb4

            lda #'B'
            jsr CHROUT
            jmp queryb2

queryb4:    lda FILE_ADDR_H,y
            ldx FILE_ADDR_L,y
            jsr HEX16OUT

            inc CURSORPOS
            lda FILE_LEN_H,y
            ldx FILE_LEN_L,y
            jsr HEX16OUT

queryb2:    jsr CROUT
            lda #34
            jsr CHROUT
queryl1:    lda FILE_NAME,y
            beq queryb3
            jsr CHROUT
            iny
            bne queryl1

queryb3:    lda #34
            jsr CHROUT
            jsr CROUT
            jmp main

;;;

errnoindex: lda #'I'
            jmp error

;;; ----------------------------------------------------------------------

changetype: lda hasaddr
            beq errnoindex
            jsr maskaddrs
            jsr CalcDirOff
            lda ADDR1
            sta FILE_TYPE,x
            jsr UpdateDirectory
            jmp queryprint


;;; ----------------------------------------------------------------------

rename:     lda hasname
            bne rens1
            jmp errnostr

rens1:      jsr CalcDirOff
            ldy stringstart
renl1:      lda LINPUT,y
            sta FILE_NAME,x
            inx
            iny
            cpy stringend
            bcc renl1
            beq renl1
            lda #0
            sta FILE_NAME,x
            jsr UpdateDirectory
            jmp queryprint


;;; ----------------------------------------------------------------------

changeinfo: jsr CalcDirOff
            lda hasaddr
            cmp #8
            beq cinfob1
            cmp #4
            beq cinfob2
            lda #'V'
            jmp error

cinfob1:    ;; two values given ADDR2 -> FILE_ADDR, ADDR1 -> FILE_LEN
            jsr swapaddrs
	    lda ADDR2
            sta FILE_LEN_L,x
            lda ADDR2+1
            sta FILE_LEN_H,x

cinfob2:    ;; one value given -- ADDR1 -> FILE_SADDR
            lda ADDR1
            sta FILE_ADDR_L,x
            lda ADDR1+1
            sta FILE_ADDR_H,x

            jsr UpdateDirectory
            jmp queryprint


;;; ----------------------------------------------------------------------

            ;; 0 0000 0000
            ;; 1 0000 000x
            ;; 2 0000 00xx
            ;; 3 0000 0xxx
            ;; 4 0000 xxxx
            ;; 5 000x xxxx
            ;; 6 00xx xxxx
            ;; 7 0xxx xxxx
            ;; 8 xxxx xxxx
            ;;
            ;; mask = 16^n - 1
            ;; mask = 2^4n - 1 ...

            .ifndef FOO1

maskaddrs:  ldy hasaddr         ; 7 bytes more code, but obviously
            lda ADDR2+1         ; a lot faster
            cpy #8
            bcs mask1
            and #15
mask1:      cpy #7
            bcs mask6
            lda #0
mask6:      sta ADDR2+1

            lda ADDR2
            cpy #6
            bcs mask2
            and #15
mask2:      cpy #5
            bcs mask7
            lda #0
mask7:      sta ADDR2

            lda ADDR1+1
            cpy #4
            bcs mask3
            and #15
mask3:      cpy #3
            bcs mask8
            lda #0
mask8:      sta ADDR1+1

            lda ADDR1
            cpy #2
            bcs mask4
            and #15
mask4:      cpy #0
            bne mask5
            lda #0
mask5:      sta ADDR1
            rts

            .endif
            .ifdef FOO1

maskaddrs:  lda #0
            sta maskbits
            sta maskbits+1
            sta maskbits+2
            sta maskbits+3
            ldy hasaddr
maskloop1:  cpy #0
            beq maskout
            ldx #4
maskloop2:  sec
            rol maskbits
            rol maskbits+1
            rol maskbits+2
            rol maskbits+3
            dex
            bne maskloop2
            dey
            bne maskloop1
maskloop3:  lda ADDR1,y
            and maskbits,y
            sta ADDR1,y
            iny
            cpy #4
            bne maskloop3
maskout:    rts

maskbits:   .word 0,0     ; okay to do as 'extended' 6522IDE is in RAM

            .endif


;;; ----------------------------------------------------------------------

zappedlist: jsr ResetDirScan

zaploop1:   jsr STOP
            beq zapexit

            jsr ReadDirectory

            ldy #255
            lda FILE_TYPE,x
            cmp #255
            beq zapprint
            cmp #0              ; if it's any kind of file, skip
            bne zapnext

zaploop2:   iny
            lda FILE_NAME,x
            bne zapprint
            inx
            cpy #31
            bne zaploop2

zapnext:    jsr BumpDirIndex
            lda DIRINDEX+1
            cmp #4
            bne zaploop1
zapexit:    jmp main

zapprint:   lda DIRINDEX+1
            ldx DIRINDEX
            jsr HEX16OUT
            inc CURSORPOS
            cpy #255
            bne zap2

            lda #'E'
            jsr CHROUT
            lda #'O'
            jsr CHROUT
            lda #'D'
            jsr CHROUT
zap3:       jsr CROUT
            jmp zapnext

zap2:       jsr CalcDirOff
            jsr printnamex
            beq zap3

;;;

printnamex: lda FILE_NAME,x
            jsr CHROUT
            inx
            cmp #0
            bne printnamex
            rts


;;; ----------------------------------------------------------------------
;;; This needs a few bit of RAM, and currently incompatible with being
;;; ROMable

            .if EPROM = 0

juggle:     lda hasaddr
            bne juggle3
            jmp errnoindex

juggle3:    jsr maskaddrs
            jsr FindName
            bcc juggle2
            jmp errnofile

            ;; read named record

juggle2:    lda DIRINDEX
            sta file1
            lda DIRINDEX+1
            sta file1+1

            ;; copy to juggrecord (swaps without updating)

            lda FILE_LEN_H,x
            sta filelen

            jsr juggleswap

            ;; read indexed record

            lda ADDR1
            sta DIRINDEX
            lda ADDR1+1
            sta DIRINDEX+1

            lda #255           ; invalidate cache if in the same block
            sta CURDIRBLK
            jsr ReadDirectory

            ;; swap dirbuffer,x and juggrecord
            ;; updatedir

            lda FILE_LEN_H,x
            cmp filelen
            bmi juggle4
            sta filelen

juggle4:    jsr juggleswap
            jsr UpdateDirectory

            ;; read named record back

            lda file1
            sta DIRINDEX
            lda file1+1
            sta DIRINDEX+1

            jsr ReadDirectory

            ;; swap dirbuffer,x and juggrecord
            ;; updatedir

            jsr juggleswap
            jsr UpdateDirectory

            ;; now to swap the segments

            lda #255   ; needed when mixing directory and sector reads..
            sta SECTORSIZE

            lda #<RAMBUF2
            sta BUFPTR
            lda #>RAMBUF2+1
            sta BUFPTR+1


juggle6:    lda filelen         ; read from file1
            sta LBA0
            lda file1
            sta LBA8
            lda file1+1
            sta LBA16
            dec BUFPTR+1        ; lda #>RAMBUF1+1 ; sta BUFPTR+1
            jsr ReadSector

            lda ADDR1           ; read from file2
            sta LBA8
            lda ADDR1+1
            sta LBA16
            inc BUFPTR+1        ; lda #>RAMBUF2+1 ; sta BUFPTR+1
            jsr ReadSector

            dec BUFPTR+1        ; lda #>RAMBUF1+1 ; sta BUFPTR+1
            jsr WriteSector     ; write file1 content to file2

            lda file1           ; write file2 content to file1
            sta LBA8
            lda file1+1
            sta LBA16
            inc BUFPTR+1        ; lda #>RAMBUF2+1 ; sta BUFPTR+1
            jsr WriteSector

            dec filelen
            lda filelen
            jsr spinner

            lda filelen
            cmp #255
            bne juggle6

            jmp main

;;;

juggleswap: ldy #0
juggswap1:  lda DIRBUFFER,x
            pha
            lda juggtemp,y
            sta DIRBUFFER,x
            pla
            sta juggtemp,y
            inx
            iny
            cpy #32
            bne juggswap1
            rts

;;;

file1:      .word 0             ; dirindex backup
filelen:    .byte 0             ; in blocks

juggtemp:   .res 32, 255

RAMBUF1:    .res 256, 255       ; keep these two together
RAMBUF2:    .res 256, 255

            .endif

;;; ----------------------------------------------------------------------
;;; TODO: make ROMable -- get rid of errorsave & errordo

fullerror:  sta errorsave
            ldx #0

ferr1:      lda errtxt,x
            beq ferrexit
            eor errorsave
            sta errordo
            inx
ferr2:      inx
            lda errtxt-1,x
            beq ferr1
            eor errordo
            cmp errtxt-1,x
            bne ferr4
            jsr CHROUT
ferr4:      jmp ferr2
ferrexit:   lda #0
            rts

;;;

errorsave:  .byte 0
errordo:    .byte 0

errtxt:     .byte '?', "NO COMMAND", 0
            .byte  34, "NO FILENAME", 0
            .byte '0', "EMPTY FILENAME", 0
            .byte 'X', "UNKNOWN FILENAME", 0
            .byte 'I', "NO INDEX", 0
            .byte 'V', "BAD VALUES", 0
            .byte 'Z', "DISK FULL", 0
            .byte 'U', "FAILED VERIFY", 0
            .byte '!', 5, "ABIOSYS V0.0 20170504",13
            .byte      "CJB@BRUSHTAIL.APANA.ORG.AU", 0
            .byte 0

;;; ----------------------------------------------------------------------
;;; Duplicates a lot of code for a function that'll never be used...

            verifycheck = FTYPETMP

verify:     lda hasname
            bne verify0
            jmp errnostr
verify0:    jsr FindName
            bcc verify4
            jmp errnofile

verify4:    jsr setloadaddr     ; FILE_ADDR in BUFPTR, LEN in ADDR2
            ldy hasaddr         ; check if an address given
            beq verify1
            lda ADDR1         ; check from this area of memory instead
            sta BUFPTR
            lda ADDR1+1
            sta BUFPTR+1

verify1:    clc
            lda BUFPTR
            adc FILE_LEN_L,x
            lda BUFPTR+1
            adc FILE_LEN_H,x

            jsr SeekSegment

verify2:    ldx #255
            lda ADDR2+1
            bne verify5
            ldx ADDR2
verify5:    stx SECTORSIZE
            lda ADDR2+1
            jsr spinner
            jsr VerifySector
            lda verifycheck
            bne verify6
            inc BUFPTR+1
            inc LBA0
            dec ADDR2+1
            lda ADDR2+1
            cmp #255
            bne verify2

            ;;

            lda verifycheck
            beq verify3
verify6:    lda #'U'
            jmp error

verify3:    jmp main

;;;

VerifySector:
	    jsr_sd_startcmd_nopush     ; Delay / synch pulses
	    ;; Send 64+17, XX, XX, XX, XX, $95
	    lda #64+17
	    jsr sd_sendbyte
	    lda #0
            sta verifycheck
	    jsr sd_sendbyte
	    lda LBA0
	    jsr sd_sendbyte
	    lda LBA8
	    jsr sd_sendbyte
	    lda LBA16
	    jsr sd_sendbyte
	    lda #$95
	    jsr sd_sendbyte
	    jsr sd_getrespbyte
	    pha                 ; Save response

sd_verify1: jsr sd_getbyte      ; Get a byte
	    cmp #$FE            ; Is it the token?
	    bne sd_verify1      ; No

            jsr hideroms

	    ldy #0
sd_verify2: jsr sd_getbyte      ; get a byte
            cpy SECTORSIZE
            beq sd_verify3
            bcs sd_verify4
sd_verify3: eor (BUFPTR),y
            ora verifycheck
            sta verifycheck
sd_verify4: iny
	    bne sd_verify2      ; Until all bytes read

            jsr showroms

            jsr sd_getbyte      ; CRC
	    jsr sd_getbyte      ; CRC

	    jsr sd_busy         ; Wait for card to be ready
	    jsr_sd_endcmd_nopush
	    pla                 ; Restore the response byte
            rts

;;; ----------------------------------------------------------------------

hideroms:   .if CBM64 = 1
            sei
            pha
            lda #54            ; for some reason banking out the KERNAL
            sta 1              ; isn't working?
            pla
            .endif
            rts

;;;

showroms:   .if CBM64 = 1
            pha
            lda #55
            sta 1
            pla
            cli
            .endif
            rts

;;; ----------------------------------------------------------------------

